package com.springsummer.information.green.beans;

import com.alibaba.fastjson.JSON;

import com.springsummer.information.green.io.ClassPathResource;
import com.springsummer.information.green.io.Resource;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

public class XmlBeanDefinitionReader implements BeanDefinitionReader {

    public static final String BEAN_ELEMENT = "bean";


    org.springframework.beans.factory.xml.XmlBeanDefinitionReader xmlBeanDefinitionReader;

    private BeanDefinitionRegistry registry;

    public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
        this.registry = registry;
    }

    @Override
    public BeanDefinitionRegistry getBeanDefinitionRegistry() {
        return this.registry;
    }

    @Override
    public int loadBeanDefinitions(String location) throws Exception {
        Collection<BeanDefinition> beanDefinitions = this.loadBeanDefinitionsInfo(location);
        return beanDefinitions.size();
    }

    @Override
    public Collection<BeanDefinition> loadBeanDefinitionsInfo(String location) throws Exception {
        if (location.contains(",")) {
            return loadBeanDefinitionsInfoMultiple(location);
        } else {
            Resource resource = new ClassPathResource(location);
            return doLoadBeanDefinitions(resource);
        }
    }

    private Collection<BeanDefinition> loadBeanDefinitionsInfoMultiple(String location) throws Exception {
        Collection<BeanDefinition> beanDefinitionCollection = new ArrayList<>(256);
        String[] locationArray = location.split(",");
        for (String loc : locationArray) {
            Resource resource = new ClassPathResource(loc);
            Collection<BeanDefinition> beanDefinitions = doLoadBeanDefinitions(resource);
            beanDefinitionCollection.addAll(beanDefinitions);
        }
        return beanDefinitionCollection;
    }


    protected Collection<BeanDefinition> doLoadBeanDefinitions(Resource resource)
            throws Exception {

        try {
            try (InputStream docStream = resource.getInputStream();) {

                Document doc = doLoadDocument(docStream);
                Collection<BeanDefinition> registedBeanDefinitions = registerBeanDefinitions(doc, resource);

                //return count;
                return registedBeanDefinitions;
            } catch (Exception e) {
                throw e;
            }

        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }


    }

    public Collection<BeanDefinition> registerBeanDefinitions(Document document, Resource resource) throws Exception {
        Element rootElement = document.getRootElement();
        Collection<BeanDefinition> beanDefinitions = doRegisterBeanDefinitions(rootElement);
        return beanDefinitions;
    }

    protected Collection<BeanDefinition> doRegisterBeanDefinitions(Element root) {
        // Any nested <beans> elements will cause recursion in this method. In
        // order to propagate and preserve <beans> default-* attributes correctly,
        // keep track of the current (parent) delegate, which may be null. Create
        // the new (child) delegate with a reference to the parent for fallback purposes,
        // then ultimately reset this.delegate back to its original (parent) reference.
        // this behavior emulates a stack of delegates without actually necessitating one.

        //Element root=doc.getDocumentElement()

        preProcessXml(root);
        Collection<BeanDefinition> beanDefinitions = parseBeanDefinitions(root);
        postProcessXml(root);

        return beanDefinitions;
    }

    protected Collection<BeanDefinition> parseBeanDefinitions(Element root) {


//            NodeList nl = root.getChildNodes();
//            for (int i = 0; i < nl.getLength(); i++) {
//                Node node = nl.item(i);
//                if (node instanceof org.w3c.dom.Element) {
//                    org.w3c.dom.Element ele = (org.w3c.dom.Element) node;
//                    if (delegate.isDefaultNamespace(ele)) {
//                        parseDefaultElement(ele, delegate);
//                    }
//                    else {
//                        delegate.parseCustomElement(ele);
//                    }
//                }
//            }

        // iterate through child elements of root
        for (Iterator<Element> it = root.elementIterator(); it.hasNext(); ) {
            Element element = it.next();
            //System.out.println(element.getName());
            // do something
            parseDefaultElement(element);

        }


        return new ArrayList<>();
    }

    private void parseDefaultElement(Element ele) {
        if (nodeNameEquals(ele, BEAN_ELEMENT)) {
            //System.out.println("BEAN_ELEMENT = " + ele.getName());
            processBeanDefinition(ele);
        }

    }

    protected void processBeanDefinition(Element ele) {
        //parseBeanDefinitionElement
        BeanDefinition beanDefinitionParsed = parseBeanDefinitionElement(ele);
        if (beanDefinitionParsed != null) {

            try {
                // registerBeanDefinition
                //BeanDefinitionReaderUtils.registerBeanDefinition(bdHolder, new SimpleBeanDefinitionRegistry());

                getBeanDefinitionRegistry().registerBeanDefinition(beanDefinitionParsed.getBeanId(), beanDefinitionParsed);

            } catch (Exception ex) {
                throw new RuntimeException(ex);
            }

        }
    }

    public BeanDefinition parseBeanDefinitionElement(Element ele) {
        //return parseBeanDefinitionElement(ele, null);
        Element beanElement = ele;

        GenericBeanDefinition genericBeanDefinition = createGenericBeanDefinition(beanElement);

        parseConstructorArgElements(ele, genericBeanDefinition);
        parsePropertyElements(ele, genericBeanDefinition);

        System.out.println("GenericBeanDefinition:" + JSON.toJSONString(genericBeanDefinition));

        return genericBeanDefinition;
    }

    private GenericBeanDefinition createGenericBeanDefinition(Element beanElement) {
        String beanId = beanElement.attributeValue("id");
        String beanClassName = beanElement.attributeValue("class");
        String beanName = beanId;
        String scopeNameAttr = beanElement.attributeValue("scope");
        String scopeName = getScopeName(scopeNameAttr);
        String initMethodName = beanElement.attributeValue("init-method");
        String destroyMethodName = beanElement.attributeValue("destroy-method");
        String factoryBeanName = beanElement.attributeValue("factory-bean");
        String factoryMethodName = beanElement.attributeValue("factory-method");
        boolean singletonFlag = scopeName.equals("singleton") ? true : false;
        boolean prototypeFlag = scopeName.equals("prototype") ? true : false;
        GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition(beanId, beanClassName, beanName, scopeName, factoryBeanName, factoryMethodName, initMethodName, destroyMethodName, singletonFlag, prototypeFlag);
        return genericBeanDefinition;
    }

    public void parseConstructorArgElements(Element beanEle, GenericBeanDefinition bd) {
        MutableConstructorArgValues constructorArgValues = new MutableConstructorArgValues();
        List<Element> elementList = beanEle.elements("constructor-arg");
        for (int i = 0; i < elementList.size(); i++) {
            Element node = elementList.get(i);
            String aType = node.attributeValue("type");
            String aName = node.attributeValue("name");
            String aValue = node.attributeValue("value");
            String indexStr = node.attributeValue("index");
            ConstructorArgValue argValue = new ConstructorArgValue(aType, aName, aValue, false);
            //System.out.println("parseConstructorArgElements index = " + indexStr + ", arg = " + JSON.toJSONString(argValue));
            constructorArgValues.addArgumentValue(Integer.parseInt(indexStr), argValue);
        }
        bd.setConstructorArgumentValues(constructorArgValues);
    }


    public void parsePropertyElements(Element beanElement, GenericBeanDefinition beanDefinition) {

        //process PropertyElements
        List<Element> propertyElements = beanElement.elements("property");
        MutablePropertyValues mutablePropertyValues = new MutablePropertyValues();
        for (Element e : propertyElements) {
            String propType = e.attributeValue("type");
            if (propType == null) {
                propType = "";
            }
            String propName = e.attributeValue("name");
            String propValue = e.attributeValue("value");
            String refStr = e.attributeValue("ref");
            boolean refFlag = parseIsRefFlag(refStr);

            // public PropertyValue parsePropertyValue(Element ele, BeanDefinition bd, String propertyName) { String elementName = (propertyName != null) ? "<property> element for property '" + propertyName + "'" : "<constructor-arg> element"; boolean hasRefAttribute = (ele.attribute(REF_ATTRIBUTE) != null); boolean hasValueAttribute = (ele.attribute(VALUE_ATTRIBUTE) != null); if (hasRefAttribute) { String refName = ele.attributeValue(REF_ATTRIBUTE); if (!StringUtils.hasText(refName)) { logger.error(elementName + " contains empty 'ref' attribute"); } PropertyValue ref = new RuntimeBeanReferencePropertyValue(ele.attributeValue(NAME_ATTRIBUTE),refName); return ref; } else if (hasValueAttribute) { PropertyValue typedStringValue = new TypedStringValuePropertyValue(ele.attributeValue(NAME_ATTRIBUTE),ele.attributeValue(VALUE_ATTRIBUTE)); return typedStringValue; } else {  } }

            PropertyValue propertyValue = new PropertyValue(propType, propName, propValue, refFlag);
            if (refFlag) {
                propertyValue = new PropertyValue(propType, propName, new RuntimeBeanReference(refStr), refFlag);
            }

            System.out.println("parsePropertyElements beanName:" + beanDefinition.getBeanId() + ", porp:" + JSON.toJSONString(propertyValue));
            mutablePropertyValues.addPropertyValue(propertyValue);
        }
        beanDefinition.setPropertyValues(mutablePropertyValues);

    }

    private boolean parseIsRefFlag(String refStr) {
        boolean refFlag = false;
        if (refStr == null) {
            return refFlag;
        }
        if (!"".equals(refStr)) {
            refFlag = true;
        }
        return refFlag;
    }

    public String getElementAttrValue(Element element) {

        return null;
    }

    private String getScopeName(String sourceScopeName) {
        String scopeName = "singleton";
        if (sourceScopeName == null) {
            return scopeName;
        }

        if (!"".equals(sourceScopeName)) {
            scopeName = sourceScopeName;
        }

        return scopeName;
    }

    public boolean nodeNameEquals(org.dom4j.Node node, String desiredName) {
        return desiredName.equals(node.getName());
    }

    protected void preProcessXml(Element root) {
    }

    protected void postProcessXml(Element root) {
    }

    protected Document doLoadDocument(InputStream inputStream) throws Exception {
        SAXReader reader = new SAXReader();
        Document document = reader.read(inputStream);
        return document;
    }

}
